﻿/*
   Copyright [2013] [Zhu, Shaojian]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QualityJudging
{
    class AnalysisLogic
    {
        List<RawDataEntry> myLogs = null;
        double[] coefficientsTime;
        double[] coefficientsEdit;

        double[] coefficientsCombineTime;
        double[] coefficientsCombineEdit;

        public double CurrentPosRatio = 0;
        public double CurrentNegRatio = 0;

        public double precision = 0;
        public double recall = 0;
        public double fvalue = 0;
        public double fvalue2 = 0;
        public double accuracy = 0;
        public double optimalY = 0;

        LinearRegression myRegression;

        double interval = 0.01;

        public AnalysisLogic2(List<RawDataEntry> Logs)
        {
            myLogs = Logs;
            myRegression = new LinearRegression();
            coefficientsTime = new double[3];
            coefficientsEdit = new double[3];
            //distribution = new double[(int)(1.0/interval)];
        }

        public List<int> getAllUserIDs()
        {
            List<int> userIds = new List<int>();
            int lastID = -1;
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                if (myLogs[i].UserID != lastID)
                {
                    lastID = myLogs[i].UserID;
                    userIds.Add(lastID);
                }
            }
            return userIds;
        }

        private double[] GetCoefficients(List<double> xArray1, List<double> xArray2, List<double> yArray)
        {
            double[] ys = new double[yArray.Count];
            double[,] xs = new double[2, yArray.Count];
            for (int i = 0; i < yArray.Count; i++)
            {
                ys[i] = yArray[i];
                xs[0, i] = xArray1[i];
                xs[1, i] = xArray2[i];
            }
            myRegression.Regress(ys, xs);
            return myRegression.C;
        }

        private double[] GetCoefficients(double[,] xs, double[] ys)
        {
            myRegression.Regress(ys, xs);
            return myRegression.C;
        }

        private void UpdateCoefCombinations(int XN, int YN, double[,] Xs, List<double> uppers, List<double> lowers, double p, int condition)
        {
            double[] Ys = new double[YN];
            for(int i=0; i<YN;i++)
            {
                Ys[i] = lowers[i]*p+uppers[i]*(1-p);
            }
            if(condition==0)
                coefficientsCombineTime = GetCoefficients(Xs, Ys);
            else
                coefficientsCombineEdit = GetCoefficients(Xs, Ys);
        }

        public int[] EvaluateThres(List<int> chosenIDs, List<int> chosenInternalIDs, bool ifTraining)
        {
            int[] TraingRecognition = new int[4];
            int[] evaluationRecognition = new int[4];
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                //System.Diagnostics.Debug.WriteLine("Processing " + (i / 5 + 1) + "/" + myLogs.Count / 5 + "...");
                RawDataEntry tempLog = myLogs[i];

                double audioLength = tempLog.audioLength;
                double audioQuality = tempLog.audioQuality;
                double picutureQuality = tempLog.pictureQuality;

                double initialTime = coefficientsTime[0] * audioLength + coefficientsTime[1] * audioQuality + coefficientsTime[2] * picutureQuality + coefficientsTime[3];
                double initialEdits = coefficientsEdit[0] * audioLength + coefficientsEdit[1] * audioQuality + coefficientsEdit[2] * picutureQuality + coefficientsEdit[3];

                List<double> times = new List<double>();
                List<double> edits = new List<double>();
                for (int j = 0; j < 5; j++)
                {
                    if (chosenInternalIDs.Contains(j))//only if chosen
                    {
                        times.Add(myLogs[i + j].timeSpent);
                        edits.Add(myLogs[i + j].answerLength);
                    }
                }

                times.Sort();
                times.Reverse();
                edits.Sort();
                edits.Reverse();

                times.Insert(0, initialTime);
                edits.Insert(0, initialEdits);

                double thresTime = 0;
                double thresEdit = 0;
                for (int j = 0; j < times.Count; j++)
                {
                    thresTime += times[j] * coefficientsCombineTime[j];
                    thresEdit += edits[j] * coefficientsCombineEdit[j];
                }
                thresTime += coefficientsCombineTime[coefficientsCombineTime.Length - 1];
                thresEdit += coefficientsCombineEdit[coefficientsCombineEdit.Length - 1];

                if (ifTraining&&chosenIDs.Contains(tempLog.UserID))
                {
                    

                    for (int j = 0; j < 5; j++)
                    {
                        if (myLogs[i + j].quality > 0)//positive
                        {
                            if (myLogs[i + j].timeSpent >= thresTime || myLogs[i + j].answerLength >= thresEdit)
                            {
                                TraingRecognition[0]++;
                            }
                            else
                                TraingRecognition[1]++;
                        }
                        else//negative
                        {
                            if (myLogs[i + j].timeSpent >= thresTime && myLogs[i + j].answerLength >= thresEdit)
                            {
                                TraingRecognition[2]++;
                            }
                            else
                                TraingRecognition[3]++;
                        }
                    }
                }
                else if (!ifTraining && !chosenIDs.Contains(tempLog.UserID))
                {
                    for (int j = 0; j < 5; j++)
                    {
                        if (myLogs[i + j].quality >= 0)//positive
                        {
                            if (myLogs[i + j].timeSpent >= thresTime || myLogs[i + j].answerLength >= thresEdit)
                            {
                                evaluationRecognition[0]++;
                            }
                            else
                                evaluationRecognition[1]++;
                        }
                        else//negative
                        {
                            if (myLogs[i + j].timeSpent >= thresTime && myLogs[i + j].answerLength >= thresEdit)
                            {
                                evaluationRecognition[2]++;
                            }
                            else
                                evaluationRecognition[3]++;
                        }
                    }
                }
            }
            if (ifTraining)
                return TraingRecognition;
            else
                return evaluationRecognition;
        }
        private double GetFitness(int[] modelFits)
        {
            double recognition1 = 0;
            double recall1 = 0;

            recognition1 = (double)modelFits[3] / (modelFits[1] + modelFits[3]);
            recall1 = (double)modelFits[3] / (modelFits[2] + modelFits[3]);

            return recognition1 * recall1 / (recognition1 + recall1) * 2;
            //return ((double)(modelFits[0] + modelFits[3])) / (double)(modelFits[0] + modelFits[1] + modelFits[2] + modelFits[3]);
        }

        private double FindBestP(int XN, int YN,
                                 double[,] XsTime, List<double> rawYTimeUppers, List<double> rawYTimeLowers,
                                 double[,] XsEdit, List<double> rawYEditUppers, List<double> rawYEditLowers,
                                 List<int> chosenIDs, List<int> chosenInternalIDs)
        {
            
            double bestFitRate = 0;
            double BestPercentage = 0;
            for(double p=interval; p<1; p+=interval)
            {
                UpdateCoefCombinations(XN, YN, XsTime, rawYTimeUppers, rawYTimeLowers, p, 0);
                UpdateCoefCombinations(XN, YN, XsEdit, rawYEditUppers, rawYEditLowers, p, 1);
                int[] modelFits = EvaluateThres(chosenIDs, chosenInternalIDs, true);
                double currentFits = GetFitness(modelFits);
                if (currentFits > bestFitRate)
                {
                    bestFitRate = currentFits;
                    BestPercentage = p;
                }
            }
            return BestPercentage;
        }
        private void GetParameters(List<int> chosenIDs, List<int> chosenInternalIDs)//, int condition)//1: use non-negative only; 2: use all
        {
            List<double> audioLengths = new List<double>();
            List<double> audioQuailties = new List<double>();
            List<double> pictureQuailties = new List<double>();

            List<double> workerTimes = new List<double>();
            List<double> workerEdits = new List<double>();

            for (int i = 0; i < myLogs.Count; i += 1)
            {
                RawDataEntry tempLog = myLogs[i];
                if (chosenInternalIDs.Contains(i%5)&&chosenIDs.Contains(tempLog.UserID))
                {
                    //if (condition == 0 && tempLog.Quality < 0)
                    //    continue;
                    //else
                    if(tempLog.quality>=0)
                    {
                        audioLengths.Add(tempLog.audioLength);
                        audioQuailties.Add(tempLog.audioQuality);
                        pictureQuailties.Add(tempLog.pictureQuality);

                        workerTimes.Add(tempLog.timeSpent);
                        workerEdits.Add(tempLog.answerLength);
                    }
                }
            }

            //string output = "Total counts: " + audioLengths.Count + "\n";
            coefficientsTime = GetCoefficients(audioLengths, audioQuailties, pictureQuailties, workerTimes);
            coefficientsEdit = GetCoefficients(audioLengths, audioQuailties, pictureQuailties, workerEdits);

            //slopeTime = GetSlope(audioLengths, textLength);
            //interTime = GetIntercept(audioLengths, workerTimes, slopeTime);
            // output += "Time=" + coefficientsTime[0] + "*AudioLength+" + coefficientsTime[1] + "*audioQuality+" + coefficientsTime[2] + "*pictureQuality+" + coefficientsTime[3] + "\n";
            // output += "Edit=" + coefficientsEdit[0] + "*AudioLength+" + coefficientsEdit[1] + "*audioQuality+" + coefficientsEdit[2] + "*pictureQuality+" + coefficientsEdit[3] + "\n";
            

            //slopeTime = GetSlope(audioLengths, textLength);
            //interTime = GetIntercept(audioLengths, workerTimes, slopeTime);
            //output += "Time=" + coefficientsTime[0] + "*Audio+" + coefficientsTime[1] + "*textLength+" +coefficientsTime[2] + "\n";
            //output += "Edit=" + coefficientsEdit[0] + "*Audio+" + coefficientsEdit[1] + "*textLength+" + coefficientsEdit[2] + "\n";
            //return output;
        }

        public double[] GetCoefficients(List<double> xArray1, List<double> xArray2, List<double> xArray3, List<double> yArray)
        {
            double[] ys = new double[yArray.Count];
            double[,] xs = new double[3, yArray.Count];
            for (int i = 0; i < yArray.Count; i++)
            {
                ys[i] = yArray[i];
                xs[0, i] = xArray1[i];
                xs[1, i] = xArray2[i];
                xs[2, i] = xArray3[i];
            }
            myRegression.Regress(ys, xs);
            return myRegression.C;
        }

        public void Evaluate(List<int> chosenIDs, List<int> chosenInternalIDs)
        {
            GetParameters(chosenIDs, chosenInternalIDs);

            List<List<double>> rawXTimevalues = new List<List<double>>();
            List<List<double>> rawXEditvalues = new List<List<double>>();
            List<double> rawYTimeUppers = new List<double>();
            List<double> rawYTimeLowers = new List<double>();
            List<double> rawYEditUppers = new List<double>();
            List<double> rawYEditLowers = new List<double>();
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                
                RawDataEntry tempLog = myLogs[i];
                if (!chosenIDs.Contains(tempLog.UserID))
                {
                    continue;
                }
                double audioLength = tempLog.audioLength;
                double audioQuality = tempLog.audioQuality;
                double picutureQuality = tempLog.pictureQuality;

                //coefficientsTime, coefficientsEdit
                double initialTime = coefficientsTime[0] * audioLength + coefficientsTime[1] * audioQuality + coefficientsTime[2] * picutureQuality + coefficientsTime[3];
                double initialEdits = coefficientsEdit[0] * audioLength + coefficientsEdit[1] * audioQuality + coefficientsEdit[2] * picutureQuality + coefficientsEdit[3];

                List<double> times = new List<double>();
                List<double> edits = new List<double>();

                double lowerBoundTime = 0;
                double upperBoundTime = 100000;
                double lowerBoundEdits = 0;
                double upperBoundEdits = 100000;
                for (int j = 0; j < 5; j++)
                {
                    if (chosenInternalIDs.Contains(j))//only if chosen
                    {
                        times.Add(myLogs[i + j].timeSpent);
                        edits.Add(myLogs[i + j].answerLength);
                        if (myLogs[i + j].quality >= 0)//non-negative
                        {
                            upperBoundTime = (upperBoundTime > myLogs[i + j].timeSpent) ? myLogs[i + j].timeSpent : upperBoundTime;
                            upperBoundEdits = (upperBoundEdits > myLogs[i + j].answerLength) ? myLogs[i + j].answerLength : upperBoundEdits;
                        }
                        else
                        {
                            lowerBoundTime = (lowerBoundTime < myLogs[i + j].timeSpent) ? myLogs[i + j].timeSpent : lowerBoundTime;
                            lowerBoundEdits = (lowerBoundEdits < myLogs[i + j].answerLength) ? myLogs[i + j].answerLength : lowerBoundEdits;
                        }
                    }
                }
                if (upperBoundTime == 100000)
                    upperBoundTime = lowerBoundTime + 1;
                if (upperBoundEdits == 100000)
                    upperBoundEdits = lowerBoundEdits + 1;

                //if (lowerBoundTime == 100000)
                //    lowerBoundTime = upperBoundTime - 1;
                //if (lowerBoundEdits == 100000)
                //    lowerBoundEdits = upperBoundEdits - 1;
                //adjust to make sure lower<upper
                lowerBoundTime = (lowerBoundTime > upperBoundTime) ? upperBoundTime -1: lowerBoundTime;
                lowerBoundEdits = (lowerBoundEdits > upperBoundEdits) ? upperBoundEdits - 1 : lowerBoundEdits;

                times.Sort();
                times.Reverse();

                edits.Sort();
                edits.Reverse();

                times.Insert(0, initialTime);
                edits.Insert(0, initialEdits);
                rawXTimevalues.Add(times);
                rawXEditvalues.Add(edits);
                rawYTimeUppers.Add(upperBoundTime);
                rawYTimeLowers.Add(lowerBoundTime);
                rawYEditUppers.Add(upperBoundEdits);
                rawYEditLowers.Add(lowerBoundEdits);
            }

            int YN = rawXTimevalues.Count;
            int XN = rawXTimevalues[0].Count;
            double[,] XsTime = new double[XN, YN];
            double[,] XsEdit = new double[XN, YN];
            for(int i=0; i<XN;i++)
            {
                for(int j=0; j<YN; j++)
                {
                    XsTime[i,j] = rawXTimevalues[j][i];
                    XsEdit[i,j] = rawXEditvalues[j][i];
                }
            }

            double BestTrainingPercentage = FindBestP(XN, YN, XsTime, rawYTimeUppers, rawYTimeLowers,
                                                        XsEdit, rawYEditUppers, rawYEditLowers,
                                                        chosenIDs, chosenInternalIDs);
            optimalY = BestTrainingPercentage;

            UpdateCoefCombinations(XN, YN, XsTime, rawYTimeUppers, rawYTimeLowers, BestTrainingPercentage, 0);
            UpdateCoefCombinations(XN, YN, XsEdit, rawYEditUppers, rawYEditLowers, BestTrainingPercentage, 1);
            int[] modelFits = EvaluateThres(chosenIDs, chosenInternalIDs, false);
            CurrentPosRatio = (double)modelFits[0] / (modelFits[0] + modelFits[1]);
            CurrentNegRatio = (double)modelFits[3] / (modelFits[3] + modelFits[2]);

            precision = (double)modelFits[0] / (modelFits[0] + modelFits[1]);
            recall = (double)modelFits[0] / (modelFits[0] + modelFits[2]);
            fvalue2 = precision * recall / (precision + recall) * 2;
            accuracy = (double)(modelFits[0] + modelFits[3]) / (modelFits[0] + modelFits[1] + modelFits[2] + modelFits[3]);

            precision = (double)modelFits[3] / (modelFits[3] + modelFits[1]);
            recall = (double)modelFits[3] / (modelFits[3] + modelFits[2]);

            //precision = (double)modelFits[0] / (modelFits[0] + modelFits[2]);
            //recall = (double)modelFits[0] / (modelFits[0] + modelFits[1]);
            //accuracy = (double)(modelFits[0] + modelFits[3]) / (modelFits[0] + modelFits[1] + modelFits[2] + modelFits[3]);
            fvalue = precision * recall / (precision + recall) * 2;

            System.Diagnostics.Debug.WriteLine("\tModel fitness: " + modelFits[0] + "\t" + modelFits[1] + "\t" + modelFits[2] + "\t" + modelFits[3] + "\t" +GetFitness(modelFits)*100+"% \n");
        }

        //one sig change is to include reliable data only for regression test for initial estimation
        public string GetInitialParameters(List<int> chosenIDs, List<int> chosenInternalIDs)//, int condition)//1: use non-negative only; 2: use all
        {
            List<double> audioLengths = new List<double>();
            List<double> audioQuailties = new List<double>();
            List<double> pictureQuailties = new List<double>();

            List<double> workerTimes = new List<double>();
            List<double> workerEdits = new List<double>();

            for (int i = 0; i < myLogs.Count; i += 1)
            {
                RawDataEntry tempLog = myLogs[i];
                if (chosenInternalIDs.Contains(i % 5) && chosenIDs.Contains(tempLog.UserID))
                {
                    //added to exclude unreliable data for initial estimation
                    if (tempLog.quality < 0)
                        continue;
                    audioLengths.Add(tempLog.audioLength);
                    audioQuailties.Add(tempLog.audioQuality);
                    pictureQuailties.Add(tempLog.pictureQuality);

                    workerTimes.Add(tempLog.timeSpent);
                    workerEdits.Add(tempLog.answerLength);
                }
            }

            string output = "Total counts: " + audioLengths.Count + "\n";
            coefficientsTime = GetCoefficients(audioLengths, audioQuailties, pictureQuailties, workerTimes);
            coefficientsEdit = GetCoefficients(audioLengths, audioQuailties, pictureQuailties, workerEdits);

            //slopeTime = GetSlope(audioLengths, textLength);
            //interTime = GetIntercept(audioLengths, workerTimes, slopeTime);
            output += "Time=" + coefficientsTime[0] + "*AudioLength+" + coefficientsTime[1] + "*audioQuality+" + coefficientsTime[2] + "*pictureQuality+" + coefficientsTime[3] + "\n";
            output += "Edit=" + coefficientsEdit[0] + "*AudioLength+" + coefficientsEdit[1] + "*audioQuality+" + coefficientsEdit[2] + "*pictureQuality+" + coefficientsEdit[3] + "\n";
            return output;
        }
    }
}
